#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/shm.h>
#include "archivos.h"
#include "logic.h"

Operacion *operaciones;
Resultado *resultados;
Proceso *procesos;
int nro_procesos;


/*-------- Procedimientos -----------------------------------------*/

void manejador_senales(int nsenal){
	int j;
	
	//SIGUSR1 cuando un proceso termina de resolver sus operaciones
	//Avisa a los que esten esperando por resultados para que continuen
	if(nsenal==SIGUSR1){
		printf("Señal de continuar\n");
		for(j=0;j<nro_procesos;j++){
			kill(procesos[j].pid,SIGCONT);
		}
	}
	
	//SIGUSR2 termina la ejecucion de los hijos y del mismo proceso padre
	//Cuando ocurre algun error durante la ejecucion
	if(nsenal==SIGUSR2){
		printf("Ha ocurrido un error durante la ejecucion\n");
		for(j=0;j<nro_procesos;j++){
			kill(procesos[j].pid,SIGTERM);	
		}
		kill(getpid(),SIGTERM);
	}
	
	//Si algun proceso hijo muere notifica del error al padre
	if(nsenal==SIGTERM || nsenal==SIGKILL){
		printf("Un proceso ha muerto\n");
		kill(getppid(),SIGUSR2);		
	}
	
	
}

//Asigna el trabajo a resolver por cada proceso
void generar_tabla_procesos(int nro_lineas){
	int i,j=0,li=0,lf=0;
	
	//Validar que la primera linea no tenga llamadas a lineas precedentes
	if(strchr(operaciones[0].operacion,'L')!=NULL){
		printf("Error: Llamada a linea precedente encontrada en la primera linea\n");
	}
	//Por cada llamada a linea precedente que encuentre le asigna a un proceso
	//desde donde hasta donde debe resolver
	for(i=0;i<nro_lineas;i++,lf++){
		if(strchr(operaciones[i].operacion,'L')!=NULL){
			procesos[j].li=li;
			procesos[j].lf=lf;
			procesos[j].listo=0;
			li=lf+1;
			j++;
		}
	}	
}

//Permite o no que un proceso se siga ejecutando si el resultado que solicita ya esta listo
void resultado_l(int linea){
	
	//Si el resultado ya fue calculado retorna,
	//Sino, espera aviso del proceso padre
	while(1){
		//printf("Resultado %i: %f\n",linea,resultados[linea].resultado1);
		if(resultados[linea].listo==1){
			//printf("Resultado %i listo\n",linea);
			return;
		}else{
			kill(getpid(),SIGSTOP);
		}
	}
}


//Sustituye las lineas precedentes por los resultados correspondientes si existen
char* sustituir_l(int nro_operacion){
	int nro_linea, digitos=0;
	char *bufferlinea;
	char buffernro[5];
	char *ptr,*ptraux;
	
	//Mientras existan lineas precedentes, sustituye
	while((ptraux=strchr(operaciones[nro_operacion].operacion,'L'))!=NULL){
		ptr=ptraux;
		bufferlinea=malloc(sizeof(operaciones[nro_operacion].operacion)+255);

		//Ignora el caracter 'L'
		ptraux++;
		//Cuenta cuantos números hay luego de la L
		while(*ptraux>='0' && *ptraux<='9'){
			digitos++;
			ptraux++;
		}
		//Copia el número de la línea precedente en buffernro
		strncpy(buffernro,ptr+1,digitos);
		//Transforma el numero de string a int
		nro_linea=atoi(buffernro);
		nro_linea-=1;
		if(nro_linea>nro_operacion){
			printf("Error: Linea precedente no puede llamar a una linea posterior\n");
			kill(getppid(),SIGUSR2);
		}else if(nro_linea==nro_operacion){
			printf("Error: Linea precedente no puede llamar a la misma operacion en la que esta\n");
			kill(getppid(),SIGUSR2);
		}	
		//Busca si el resultado ya fue cargado, sino espera aviso del padre
		resultado_l(nro_linea);	
		//Copia la parte antes de la Lx en el bufferlinea
		strncpy(bufferlinea,operaciones[nro_operacion].operacion,ptr-operaciones[nro_operacion].operacion);
		bufferlinea[ptr-operaciones[nro_operacion].operacion]='\0';
		
		//Verifica si se solicita el segundo resultado de la línea precedente o no
		if(*ptraux=='_'){
			if(ptraux[1]=='2'){
				//Copia el resultado de Lx_2 y la parte posterior a Lx
				sprintf(bufferlinea,"%s%f%s",bufferlinea,resultados[nro_linea].resultado2,ptraux+2);			
			}else if(ptraux[1]=='1'){
				//Copia el resultado de Lx_1 y la parte posterior a Lx
				sprintf(bufferlinea,"%s%f%s",bufferlinea,resultados[nro_linea].resultado1,ptraux+2);		
			}else{
				printf("Error: Resultado de línea debe estar entre 1 y 2\n");
				printf("%s\n",operaciones[nro_operacion].operacion);
				kill(getppid(),SIGUSR2);
			}		
		}else{
			//Copia el resultado de Lx y la parte posterior a Lx	
			sprintf(bufferlinea,"%s%f%s",bufferlinea,resultados[nro_linea].resultado1,ptraux);
		}

		//Redimensionar el string operacion y devolver memoria de bufferlinea
		free(operaciones[nro_operacion].operacion);
		operaciones[nro_operacion].operacion=malloc(sizeof(strlen(bufferlinea)));
		strcpy(operaciones[nro_operacion].operacion,bufferlinea);
		free(bufferlinea);		
	}
	return operaciones[nro_operacion].operacion;
		
}

int asignar_memoria( char *nombre_arch ){
    int lineas=0,icl_shm;

    //Asignar memoria y realizar configuracion inicial
    lineas=nro_lineas(nombre_arch,&nro_procesos);
    operaciones=leer_archivo(nombre_arch,lineas);

    icl_shm=shmget(IPC_PRIVATE,sizeof(Resultado)*lineas+sizeof(Proceso)*nro_procesos,0666|IPC_CREAT);
    resultados=(Resultado *)shmat(icl_shm,NULL,SHM_RND);
    procesos=(Proceso*)resultados+sizeof(Resultado)*lineas;

    generar_tabla_procesos(lineas);

    return icl_shm;
}

void ejecutar_procesos(int timming){
    int i,j;

    //Asignar señales al proceso padre
    signal(SIGUSR1,manejador_senales);
    signal(SIGUSR2,manejador_senales);
    signal(SIGALRM,manejador_senales);

    //Crear y ejecutar cada proceso
    for(i=0;i<nro_procesos;i++){
        //Tiempo de ejecucion del padre
        if(timming!=0){
            alarm(timming);
            pause();
        }

        procesos[i].pid=fork();
        if(procesos[i].pid==0){
            /*                 Ejecucion de proceso hijo                     */

            //Asignar señales a los hijos
            signal(SIGKILL,manejador_senales);
            signal(SIGTERM,manejador_senales);

            //Cada proceso ejecuta desde-hasta la operacion que le toco
            for(j=procesos[i].li;j<=procesos[i].lf;j++){
                //Tiempo de ejecucion de los hijos
                if(timming!=0){
                    alarm(timming);
                    pause();
                }
                printf("Proceso %i Operando: %s\n",getpid(),operaciones[j].operacion);
                sustituir_l(j);
                resultados[j].resultado1=10+j;
                resultados[j].listo=1;
            }
            /* //Validar conversion
            printf("luego de la conversion:\n");
            for(j=procesos[i].li;j<=procesos[i].lf;j++){
                printf("%s\n",operaciones[j].operacion);
            }*/

            //Avisa que ya su ejecución termino al padre
            procesos[i].listo=1;
            kill(getppid(),SIGUSR1);
            exit(0);

            /*                 Fin de ejecucion de proceso hijo                 */
        }
    }

    //Espera a que los hijos terminen su ejecucion
    for(i=0;i<nro_procesos;i++){
        wait(NULL);
        //printf("Salio un proceso\n");
    }
}

void devolver_memoria(int icl_shm){
    int lineas=0;

    //Devolver memoria asignada
    devolver_memoria_vector(operaciones,lineas);
    shmdt(resultados);
    shmctl(icl_shm,IPC_RMID,NULL);
    printf("chao\n");
}

/*-------- Main ---------------------------------------------------*/



int proceso_z(char *nombre_arch,int timming){
//int main(int argc,char **argv){
    int lineas=0,i,j,icl_shm;
    //char *nombre_arch="ejemplo.txt";

	//Asignar memoria y realizar configuracion inicial
	lineas=nro_lineas(nombre_arch,&nro_procesos);
	operaciones=leer_archivo(nombre_arch,lineas);	

	icl_shm=shmget(IPC_PRIVATE,sizeof(Resultado)*lineas+sizeof(Proceso)*nro_procesos,0666|IPC_CREAT);
    resultados=(Resultado *)shmat(icl_shm,NULL,SHM_RND);
    procesos=(Proceso*)resultados+sizeof(Resultado)*lineas;

	generar_tabla_procesos(lineas);

	//printf("%i\n",getpid());
	
	for(i=0;i<lineas;i++){
		printf("%s\n",operaciones[i].operacion);
	}
	
	for(i=0;i<nro_procesos;i++){
		printf("Proceso %i: LI:%i LF:%i\n",i,procesos[i].li,procesos[i].lf);
	}
	
	//Asignar señales al proceso padre
	signal(SIGUSR1,manejador_senales);
	signal(SIGUSR2,manejador_senales);
	signal(SIGALRM,manejador_senales);
	
	//Crear y ejecutar cada proceso
	for(i=0;i<nro_procesos;i++){
		//Tiempo de ejecucion del padre
		if(timming!=0){
			alarm(timming);
			pause();		
		}	
		
		procesos[i].pid=fork();
		if(procesos[i].pid==0){
			/*                 Ejecucion de proceso hijo                     */
			
			//Asignar señales a los hijos
			signal(SIGKILL,manejador_senales);
			signal(SIGTERM,manejador_senales);
			
			//Cada proceso ejecuta desde-hasta la operacion que le toco
			for(j=procesos[i].li;j<=procesos[i].lf;j++){
				//Tiempo de ejecucion de los hijos
				if(timming!=0){
					alarm(timming);
					pause();				
				}			
				printf("Proceso %i Operando: %s\n",getpid(),operaciones[j].operacion);
				sustituir_l(j);
				resultados[j].resultado1=10+j;
				resultados[j].listo=1;			
			}
            /* //Validar conversion
			printf("luego de la conversion:\n");
			for(j=procesos[i].li;j<=procesos[i].lf;j++){
				printf("%s\n",operaciones[j].operacion);
            }*/
			
			//Avisa que ya su ejecución termino al padre
			procesos[i].listo=1;		
			kill(getppid(),SIGUSR1);
			exit(0);
			
			/*                 Fin de ejecucion de proceso hijo                 */
		}
	}
	
	//Espera a que los hijos terminen su ejecucion
	for(i=0;i<nro_procesos;i++){
		wait(NULL);
		//printf("Salio un proceso\n");
	}
	
	//Devolver memoria asignada	
	devolver_memoria_vector(operaciones,lineas);
	shmdt(resultados);
	shmctl(icl_shm,IPC_RMID,NULL);
    printf("chao\n");
    return EXIT_SUCCESS;
}
